#include "StringUtil.h"

#include <stdint.h>

#include "Configuration.h"
#include "MiscUtil.h"
#include "ldconvert.h"
#include "main.h"

QString ToLongDoubleString(const void* buffer) {
#if 0
    char str[32];
    ld2str(buffer, str);
    return str;
#else
  return QString("%1").arg(*(double*)buffer);
#endif
}

QString EscapeCh(QChar ch) {
  switch (ch.unicode()) {
    case '\0':
      return "\\0";
    case '\t':
      return "\\t";
    case '\f':
      return "\\f";
    case '\v':
      return "\\v";
    case '\n':
      return "\\n";
    case '\r':
      return "\\r";
    case '\\':
      return "\\\\";
    case '\"':
      return "\\\"";
    case '\a':
      return "\\a";
    case '\b':
      return "\\b";
    default:
      return QString(1, ch);
  }
}

QString fillValue(const char* value, int valsize,
                  bool bFpuRegistersLittleEndian) {
  if (bFpuRegistersLittleEndian)
    return QString(QByteArray(value, valsize).toHex()).toUpper();
  else  // Big Endian
    return QString(ByteReverse(QByteArray(value, valsize)).toHex()).toUpper();
}

QString composeRegTextXMM(const char* value, int mode) {
  bool bFpuRegistersLittleEndian =
      ConfigBool("Gui", "FpuRegistersLittleEndian");
  QString valueText;
  switch (mode) {
    default:
    case 0: {
      valueText = fillValue(value, 16, bFpuRegistersLittleEndian);
    } break;
    case 2: {
      const double* dbl_values = reinterpret_cast<const double*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = ToDoubleString(&dbl_values[0]) + ' ' +
                    ToDoubleString(&dbl_values[1]);
      else  // Big Endian
        valueText = ToDoubleString(&dbl_values[1]) + ' ' +
                    ToDoubleString(&dbl_values[0]);
    } break;
    case 1: {
      const float* flt_values = reinterpret_cast<const float*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = ToFloatString(&flt_values[0]) + ' ' +
                    ToFloatString(&flt_values[1]) + ' ' +
                    ToFloatString(&flt_values[2]) + ' ' +
                    ToFloatString(&flt_values[3]);
      else  // Big Endian
        valueText = ToFloatString(&flt_values[3]) + ' ' +
                    ToFloatString(&flt_values[2]) + ' ' +
                    ToFloatString(&flt_values[1]) + ' ' +
                    ToFloatString(&flt_values[0]);
    } break;
    case 9: {
      if (bFpuRegistersLittleEndian)
        valueText = fillValue(value) + ' ' + fillValue(value + 1 * 2) + ' ' +
                    fillValue(value + 2 * 2) + ' ' + fillValue(value + 3 * 2) +
                    ' ' + fillValue(value + 4 * 2) + ' ' +
                    fillValue(value + 5 * 2) + ' ' + fillValue(value + 6 * 2) +
                    ' ' + fillValue(value + 7 * 2);
      else  // Big Endian
        valueText = fillValue(value + 7 * 2) + ' ' + fillValue(value + 6 * 2) +
                    ' ' + fillValue(value + 5 * 2) + ' ' +
                    fillValue(value + 4 * 2) + ' ' + fillValue(value + 3 * 2) +
                    ' ' + fillValue(value + 2 * 2) + ' ' +
                    fillValue(value + 1 * 2) + ' ' + fillValue(value);
    } break;
    case 3: {
      const short* sword_values = reinterpret_cast<const short*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(sword_values[0]) + ' ' +
                    QString::number(sword_values[1]) + ' ' +
                    QString::number(sword_values[2]) + ' ' +
                    QString::number(sword_values[3]) + ' ' +
                    QString::number(sword_values[4]) + ' ' +
                    QString::number(sword_values[5]) + ' ' +
                    QString::number(sword_values[6]) + ' ' +
                    QString::number(sword_values[7]);
      else  // Big Endian
        valueText = QString::number(sword_values[7]) + ' ' +
                    QString::number(sword_values[6]) + ' ' +
                    QString::number(sword_values[5]) + ' ' +
                    QString::number(sword_values[4]) + ' ' +
                    QString::number(sword_values[3]) + ' ' +
                    QString::number(sword_values[2]) + ' ' +
                    QString::number(sword_values[1]) + ' ' +
                    QString::number(sword_values[0]);
    } break;
    case 6: {
      const unsigned short* uword_values =
          reinterpret_cast<const unsigned short*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(uword_values[0]) + ' ' +
                    QString::number(uword_values[1]) + ' ' +
                    QString::number(uword_values[2]) + ' ' +
                    QString::number(uword_values[3]) + ' ' +
                    QString::number(uword_values[4]) + ' ' +
                    QString::number(uword_values[5]) + ' ' +
                    QString::number(uword_values[6]) + ' ' +
                    QString::number(uword_values[7]);
      else  // Big Endian
        valueText = QString::number(uword_values[7]) + ' ' +
                    QString::number(uword_values[6]) + ' ' +
                    QString::number(uword_values[5]) + ' ' +
                    QString::number(uword_values[4]) + ' ' +
                    QString::number(uword_values[3]) + ' ' +
                    QString::number(uword_values[2]) + ' ' +
                    QString::number(uword_values[1]) + ' ' +
                    QString::number(uword_values[0]);
    } break;
    case 10: {
      if (bFpuRegistersLittleEndian)
        valueText = fillValue(value, 4) + ' ' + fillValue(value + 1 * 4, 4) +
                    ' ' + fillValue(value + 2 * 4, 4) + ' ' +
                    fillValue(value + 3 * 4, 4);
      else  // Big Endian
        valueText = fillValue(value + 3 * 4, 4) + ' ' +
                    fillValue(value + 2 * 4, 4) + ' ' +
                    fillValue(value + 1 * 4, 4) + ' ' + fillValue(value, 4);
    } break;
    case 4: {
      const int* sdword_values = reinterpret_cast<const int*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(sdword_values[0]) + ' ' +
                    QString::number(sdword_values[1]) + ' ' +
                    QString::number(sdword_values[2]) + ' ' +
                    QString::number(sdword_values[3]);
      else  // Big Endian
        valueText = QString::number(sdword_values[3]) + ' ' +
                    QString::number(sdword_values[2]) + ' ' +
                    QString::number(sdword_values[1]) + ' ' +
                    QString::number(sdword_values[0]);
    } break;
    case 7: {
      const unsigned int* udword_values =
          reinterpret_cast<const unsigned int*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(udword_values[0]) + ' ' +
                    QString::number(udword_values[1]) + ' ' +
                    QString::number(udword_values[2]) + ' ' +
                    QString::number(udword_values[3]);
      else  // Big Endian
        valueText = QString::number(udword_values[3]) + ' ' +
                    QString::number(udword_values[2]) + ' ' +
                    QString::number(udword_values[1]) + ' ' +
                    QString::number(udword_values[0]);
    } break;
    case 11: {
      if (bFpuRegistersLittleEndian)
        valueText = fillValue(value, 8) + ' ' + fillValue(value + 8, 8);
      else  // Big Endian
        valueText = fillValue(value + 8, 8) + ' ' + fillValue(value, 8);
    } break;
    case 5: {
      const long long* sqword_values =
          reinterpret_cast<const long long*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(sqword_values[0]) + ' ' +
                    QString::number(sqword_values[1]);
      else  // Big Endian
        valueText = QString::number(sqword_values[1]) + ' ' +
                    QString::number(sqword_values[0]);
    } break;
    case 8: {
      const unsigned long long* uqword_values =
          reinterpret_cast<const unsigned long long*>(value);
      if (bFpuRegistersLittleEndian)
        valueText = QString::number(uqword_values[0]) + ' ' +
                    QString::number(uqword_values[1]);
      else  // Big Endian
        valueText = QString::number(uqword_values[1]) + ' ' +
                    QString::number(uqword_values[0]);
    } break;
  }
  return valueText;
}

QString composeRegTextYMM(const char* value, int mode) {
  bool bFpuRegistersLittleEndian =
      ConfigBool("Gui", "FpuRegistersLittleEndian");
  if (mode == 0)
    return fillValue(value, 32, bFpuRegistersLittleEndian);
  else if (bFpuRegistersLittleEndian)
    return composeRegTextXMM(value, mode) + ' ' +
           composeRegTextXMM(value + 16, mode);
  else
    return composeRegTextXMM(value + 16, mode) + ' ' +
           composeRegTextXMM(value, mode);
}

QString GetDataTypeString(const void* buffer, duint size, ENCODETYPE type) {
  switch (type) {
    case enc_byte:
      return ToIntegralString<unsigned char>(buffer);
    case enc_word:
      return ToIntegralString<unsigned short>(buffer);
    case enc_dword:
      return ToIntegralString<unsigned int>(buffer);
    case enc_fword:
      return QString(ByteReverse(QByteArray((const char*)buffer, 6)).toHex());
    case enc_qword:
      return ToIntegralString<unsigned long long int>(buffer);
    case enc_tbyte:
      return QString(ByteReverse(QByteArray((const char*)buffer, 10)).toHex());
    case enc_oword:
      return QString(ByteReverse(QByteArray((const char*)buffer, 16)).toHex());
    case enc_mmword:
      return QString(QByteArray((const char*)buffer, size).toHex());
    case enc_xmmword:
      return composeRegTextXMM((const char*)buffer,
                               ConfigUint("Gui", "SIMDRegistersDisplayMode"));
    case enc_ymmword:
      return composeRegTextYMM((const char*)buffer,
                               ConfigUint("Gui", "SIMDRegistersDisplayMode"));
    case enc_real4:
      return ToFloatString(buffer);
    case enc_real8:
      return ToDoubleString(buffer);
    case enc_real10:
      return ToLongDoubleString(buffer);
    case enc_ascii:
      return EscapeCh(*(const char*)buffer);
    case enc_unicode:
      return EscapeCh(*(const wchar_t*)buffer);
    default:
      return ToIntegralString<unsigned char>(buffer);
  }
}

QString ToDateString(const QDate& date) {
#if 0
  static const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  return QString().sprintf("%s %d %d", months[date.month() - 1], date.day(),
                           date.year());
#else
  return date.toString(Qt::ISODate);
#endif
}

QString FILETIMEToDate(const FILETIME& date) {
  FILETIME localdate;
  FileTimeToLocalFileTime(&date, &localdate);
  SYSTEMTIME systime;
  FileTimeToSystemTime(&localdate, &systime);
  QDate qdate = QDate(systime.wYear, systime.wMonth, systime.wDay);
  quint64 time100ns = (quint64)localdate.dwHighDateTime << 32 |
                      (quint64)localdate.dwLowDateTime;
  time100ns %= (1000ull * 60ull * 60ull * 24ull * 10000ull);
  localdate.dwHighDateTime = time100ns >> 32;
  localdate.dwLowDateTime = time100ns & 0xFFFFFFFF;
  if (qdate != QDate::currentDate())
    return QLocale(QString(currentLocale)).toString(qdate) +
           FILETIMEToTime(localdate);
  else  // today
    return FILETIMEToTime(localdate);
}

bool GetCommentFormat(duint addr, QString& comment, bool* autoComment) {
  comment.clear();
  char commentData[MAX_COMMENT_SIZE] = "";
  if (!DbgGetCommentAt(nullptr, addr, commentData)) return false;
  auto a = *commentData == '\1';
  if (autoComment) *autoComment = a;
  if (!strstr(commentData, "{")) {
    comment = commentData + a;
    return true;
  }
  char commentFormat[MAX_SETTING_SIZE] = "";
  if (DbgFunctions()->StringFormatInline(commentData + a, MAX_SETTING_SIZE,
                                         commentFormat))
    comment = commentFormat;
  else
    comment = commentData + a;
  return true;
}
